package org.jlambda.list;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import org.jlambda.util.LogUtils;

public class MemoizingList<T> extends List<T> implements Iterable<T> {

	LinkedList<T> list = new LinkedList<T>(); 
	
	public MemoizingList(ListDeclaration<T> de){
		super(de);
	}
	
	public boolean isMemoized(){
		return true;
	}
	
	/**
	 * Does not allow removes.  This reduces side effects.
	 * 
	 * Repeated calls to finiteIterator should always yield the same results.
	 * 
	 * @author TGDTWCH1
	 *
	 */
	class safeIterator implements Iterator<T> {

		Iterator<T> internal =null;
		public safeIterator(Iterator itr){
			internal = itr;
		}
		
		public boolean hasNext() {
			return internal.hasNext();
		}

		public T next() {
			return internal.next();
		}

		// do nothing.
		public void remove() {
			
		}

		public void toStringBuf(StringBuilder buf){
			buf.append("[");
			while (hasNext()) {
				Object element = (Object) next();
				buf.append(element);
				buf.append(" , ");
			}
			buf.append("]");
		}
		
		@Override
		public String toString() {
			StringBuilder buf = new StringBuilder();
			toStringBuf(buf);
			return buf.toString();
		}
	}
	
	
	/**
	 * Returns an iterator to the underlying memoized list.
	 * 
	 * @return
	 */
	final public Iterator<T> calculatedIterator(){
		return new safeIterator(getList().iterator());
	}
	
	/**
	 * Appends the list to this list and returns it.
	 * @param ap
	 * @return
	 */
	final public MemoizingList<T> append(Collection<T> ap){
		if (ap != null) {
			getList().addAll(ap);
		}
		return this;
	}
	
	/**
	 * Appends the list as an object of this list.
	 * @param ap
	 * @return
	 */
	final public MemoizingList<T> appendo(T ap){
		getList().add(ap);
		return this;
	}
	
	/**
	 * Appends the list to this list and returns it.
	 * @param ap
	 * @return
	 */
	final public MemoizingList<T> append(T ap){
		if (ap != null) {
			getList().add(ap);
		}
		return this;
	}
	
	/**
	 * Appends the list to this list and returns it.
	 * @param ap
	 * @return
	 */
	final public MemoizingList<T> append(MemoizingList<T> ap){
		return append(ap.getList());
	}
	
	public Iterator<T> iterator() {
		return new lazyItr();
	}
	
	public LinkedList<T> getList(){
		return list;
	}
	
	final private void growTo(int size) {
		int i = getList().size();
		for(;i<size;i++){
			getNextItem();
		}
	}
	
	final private T getNextItemNonAdd(){
		T res = getDecl().getNext();
		return res;
	}
	
	final private T getNextItem(){
		T res = getDecl().getNext();
		if (!getDecl().isEOL()){
			list.add(res);
		}
		return res;
	}
	
	/**
	 * Makes a best effort to take this many from this List
	 * 
	 * When lists have reached finite length it will return
	 * an empty list if first>size and first->size if second>size.
	 * 
	 * @param first
	 * @param second
	 * @return 
	 * @throws Exception 
	 */
	final public ArrayList<T> take(int first, int second) {
		java.util.List list = getList();
		if (first>=list.size()){
			growTo(first + 1);  // size if 0 -> add one, makes size 1 and index 0 available
		}
		// may be at end of generated sequence
		if (first>=list.size()){
			return new ArrayList<T>();
		}
		
		if (second>list.size()){
			growTo(second);
		}
		
		// may be at end of generated sequence
		if (second>list.size()){
			second = list.size(); // shrink result
		}
		
		return new ArrayList<T>(list.subList(first, second));
	}
	
	/**
	 * Makes a best effort to take this position from this List
	 * 
	 * When lists have reached finite length it will return
	 * EOL if first>size
	 * 
	 * @param first
	 * @return 
	 * @throws NoSuchElementException when there are no more elements
	 */
	final public T get(final int first) {
		java.util.List<T> list = getList();
		if (first>=list.size()){
			growTo(first+1); // size if 0 -> add one, makes size 1 and index 0 available
		}
		// may be at end of generated sequence
		if (first>=list.size()){
			throw new NoSuchElementException("get of "+first+" is more than the "+list.size()+" elements in the list");
		}
		
		return list.get(first);
	}

	class lazyItr implements Iterator<T> {
		boolean nexted = true; // have to start it off in case of no elements
		T object = null;
		boolean eold = false;
		
		boolean eoi = false;
		
		ListIterator<T> itr = getList().listIterator();
		
		private T add(){
			T res = getNextItemNonAdd();
			
			if (getDecl().isEOL()) {
				eold = true;
			} else {
				itr.add(res);
			}
			return res;
		}
		
		public boolean hasNext() {
			if (eold) return false;
			
			if (itr.hasNext()) {
				return true;
			}

			if (!nexted) return true;
			
			// no more in the itr :-< ListIterators don't increase their size after an add, BUG me thinks.
			eoi = true;
			
			object = add();
			nexted = false;
			return !eold;
		}

		public T next() {
			if (eold) throw new NoSuchElementException("List has no more items");
			
			T ob = null;
			
			if (!eoi) {
				if (itr.hasNext()) {
					ob = itr.next();
					return ob; // from itr is good.					 
				}
				else {
					eoi = true;				
				}
			} 

			if (nexted) {
				// called next again without hasNext 
				ob = add();
			} else {
				// already got it 
				ob = object;
			}
			if (eold || getDecl().isEOL()) {
				throw new NoSuchElementException("List has no more items");
			}			
			nexted = true;
				
			return ob;
		}

		public void remove() {
			
		}
	}

	class llazyItr implements Iterator<T> {
		int i = -1;
		
		public boolean hasNext() {
			get(i+1);
			
			if (getDecl().isEOL()) return false;
			
			return true;
		}

		public T next() {
			T ob = null;
			try {
				ob = get(++i);
			} catch (Exception e) {
				throw new NoSuchElementException("List has no more items");
			}	
			if (getDecl().isEOL()) throw new NoSuchElementException("List has no more items");
				
			return ob;
		}

		public void remove() {
			
		}
	}
	

	final public int getCurrentSize(){
		return getList().size();
	}
	
	final public void toStringBuf(StringBuilder buf, int tab){
		LogUtils.getTab(tab, buf);
		safeIterator itr = (safeIterator) calculatedIterator();
		itr.toStringBuf(buf);
	}
	
	@Override
	final public String toString() {
		return calculatedIterator().toString();
	}

}
